/*
 * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package org.eclipse.imagen.media.opimage;

import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.awt.image.DataBufferUShort;
import java.awt.image.MultiPixelPackedSampleModel;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;
import java.util.Map;
import org.eclipse.imagen.BorderExtender;
import org.eclipse.imagen.ImageLayout;
import org.eclipse.imagen.Interpolation;
import org.eclipse.imagen.JAI;
import org.eclipse.imagen.util.Range;

/** An OpImage subclass that performs nearest-neighbour Affine mapping */
final class AffineNearestBinaryOpImage extends AffineNearestOpImage {

    // The background
    private int black = 0;

    // Since this operation deals with packed binary data, we do not need
    // to expand the IndexColorModel
    private static Map configHelper(Map configuration) {

        Map config;

        if (configuration == null) {

            config = new RenderingHints(JAI.KEY_REPLACE_INDEX_COLOR_MODEL, Boolean.FALSE);

        } else {

            config = configuration;

            if (!(config.containsKey(JAI.KEY_REPLACE_INDEX_COLOR_MODEL))) {
                RenderingHints hints = (RenderingHints) configuration;
                config = (RenderingHints) hints.clone();
                config.put(JAI.KEY_REPLACE_INDEX_COLOR_MODEL, Boolean.FALSE);
            }
        }

        return config;
    }

    /**
     * Constructs an AffineNearestBinaryOpImage from a RenderedImage source,
     *
     * @param source a RenderedImage.
     * @param layout an ImageLayout optionally containing the tile grid layout, SampleModel, and ColorModel, or null.
     * @param interp an Interpolation object to use for resampling
     * @param transform the desired AffineTransform.
     */
    public AffineNearestBinaryOpImage(
            RenderedImage source,
            BorderExtender extender,
            Map config,
            ImageLayout layout,
            AffineTransform transform,
            Interpolation interp,
            double[] backgroundValues) {
        super(source, extender, configHelper(config), layout, transform, interp, backgroundValues);

        // Propagate source's ColorModel and SampleModel but change tile size.
        if (layout != null) {
            colorModel = layout.getColorModel(source);
        } else {
            colorModel = source.getColorModel();
        }
        sampleModel = source.getSampleModel().createCompatibleSampleModel(tileWidth, tileHeight);

        // Set the background to color to 1 if the color
        // model specifies that as "black", otherwise 0.
        //
        // This cause problem on the background (Bug 4380285): because
        // (1) if the tile does not intersect with the source, zeroed tile(white
        // is created; (2) if the line does not intersect with the source,
        // zeroed-line is created; (3) if the image area does not equal to
        // the whole destination rectangle, some white line will be displayed.
        //
        // if (colorModel.getRGB(1) == 0xff000000) {
        //    black = 1;
        // }
    }

    /**
     * Performs an affine transform on a specified rectangle. The sources are cobbled.
     *
     * @param sources an array of source Rasters, guaranteed to provide all necessary source data for computing the
     *     output.
     * @param dest a WritableRaster tile containing the area to be computed.
     * @param destRect the rectangle within dest to be processed.
     */
    protected void computeRect(Raster[] sources, WritableRaster dest, Rectangle destRect) {
        Raster source = sources[0];

        switch (source.getSampleModel().getDataType()) {
            case DataBuffer.TYPE_BYTE:
                byteLoop(source, dest, destRect);
                break;

            case DataBuffer.TYPE_INT:
                intLoop(source, dest, destRect);
                break;

            case DataBuffer.TYPE_SHORT:
            case DataBuffer.TYPE_USHORT:
                shortLoop(source, dest, destRect);
                break;
        }
    }

    private void byteLoop(Raster source, WritableRaster dest, Rectangle destRect) {
        float src_rect_x1 = source.getMinX();
        float src_rect_y1 = source.getMinY();
        float src_rect_x2 = src_rect_x1 + source.getWidth();
        float src_rect_y2 = src_rect_y1 + source.getHeight();

        MultiPixelPackedSampleModel sourceSM = (MultiPixelPackedSampleModel) source.getSampleModel();
        DataBufferByte sourceDB = (DataBufferByte) source.getDataBuffer();
        int sourceTransX = source.getSampleModelTranslateX();
        int sourceTransY = source.getSampleModelTranslateY();
        int sourceDataBitOffset = sourceSM.getDataBitOffset();
        int sourceScanlineStride = sourceSM.getScanlineStride();

        MultiPixelPackedSampleModel destSM = (MultiPixelPackedSampleModel) dest.getSampleModel();
        DataBufferByte destDB = (DataBufferByte) dest.getDataBuffer();
        int destMinX = dest.getMinX();
        int destMinY = dest.getMinY();
        int destTransX = dest.getSampleModelTranslateX();
        int destTransY = dest.getSampleModelTranslateY();
        int destDataBitOffset = destSM.getDataBitOffset();
        int destScanlineStride = destSM.getScanlineStride();

        byte[] sourceData = sourceDB.getData();
        int sourceDBOffset = sourceDB.getOffset();

        byte[] destData = destDB.getData();
        int destDBOffset = destDB.getOffset();

        Point2D dst_pt = new Point2D.Float();
        Point2D src_pt = new Point2D.Float();

        int dst_min_x = destRect.x;
        int dst_min_y = destRect.y;
        int dst_max_x = destRect.x + destRect.width;
        int dst_max_y = destRect.y + destRect.height;

        int incyStride = incy * sourceScanlineStride;
        int incy1Stride = incy1 * sourceScanlineStride;

        black = ((int) backgroundValues[0]) & 1;

        for (int y = dst_min_y; y < dst_max_y; y++) {
            // Backward map the first point in the line
            // The energy is at the (pt_x + 0.5, pt_y + 0.5)
            dst_pt.setLocation((double) dst_min_x + 0.5, (double) y + 0.5);
            mapDestPoint(dst_pt, src_pt);

            // Get the mapped source coordinates
            float s_x = (float) src_pt.getX();
            float s_y = (float) src_pt.getY();

            // Floor to get the integral coordinate
            int s_ix = (int) Math.floor(s_x);
            int s_iy = (int) Math.floor(s_y);

            double fracx = s_x - (double) s_ix;
            double fracy = s_y - (double) s_iy;

            int ifracx = (int) Math.floor(fracx * geom_frac_max);
            int ifracy = (int) Math.floor(fracy * geom_frac_max);

            int start_s_ix = s_ix;
            int start_s_iy = s_iy;
            int start_ifracx = ifracx;
            int start_ifracy = ifracy;

            // Compute clipMinX, clipMinY
            Range clipRange = performScanlineClipping(
                    src_rect_x1,
                    src_rect_y1,
                    // Last point in the source is
                    // x2 = x1 + width - 1
                    // y2 = y1 + height - 1
                    src_rect_x2 - 1,
                    src_rect_y2 - 1,
                    s_ix,
                    s_iy,
                    ifracx,
                    ifracy,
                    dst_min_x,
                    dst_max_x,
                    0,
                    0,
                    0,
                    0);
            int clipMinX = ((Integer) clipRange.getMinValue()).intValue();
            int clipMaxX = ((Integer) clipRange.getMaxValue()).intValue();

            if (clipMinX > clipMaxX) continue;

            int destYOffset = (y - destTransY) * destScanlineStride + destDBOffset;
            int destXOffset = destDataBitOffset + (dst_min_x - destTransX);

            int sourceYOffset = (s_iy - sourceTransY) * sourceScanlineStride + sourceDBOffset;
            int sourceXOffset = s_ix - sourceTransX + sourceDataBitOffset;

            for (int x = dst_min_x; x < clipMinX; x++) {

                if (setBackground) {
                    int dindex = destYOffset + (destXOffset >> 3);
                    int dshift = 7 - (destXOffset & 7);
                    int delement = destData[dindex];
                    delement |= black << dshift;
                    destData[dindex] = (byte) delement;
                }

                // walk
                if (ifracx < ifracdx1) {
                    /*
                    // DEBUG
                                  s_ix += incx;
                    */
                    ifracx += ifracdx;
                    sourceXOffset += incx;
                } else {
                    /*
                    // DEBUG
                                  s_ix += incx1;
                    */
                    ifracx -= ifracdx1;
                    sourceXOffset += incx1;
                }

                if (ifracy < ifracdy1) {
                    /*
                    // DEBUG
                                  s_iy += incy;
                    */
                    ifracy += ifracdy;
                    sourceYOffset += incyStride;
                } else {
                    /*
                    // DEBUG
                                  s_iy += incy1;
                    */
                    ifracy -= ifracdy1;
                    sourceYOffset += incy1Stride;
                }

                ++destXOffset;
            }

            for (int x = clipMinX; x < clipMaxX; x++) {
                int sindex = sourceYOffset + (sourceXOffset >> 3);
                byte selement = sourceData[sindex];
                int val = (selement >> (7 - (sourceXOffset & 7))) & 1;

                int dindex = destYOffset + (destXOffset >> 3);
                int dshift = 7 - (destXOffset & 7);
                int delement = destData[dindex];
                delement |= val << dshift;
                destData[dindex] = (byte) delement;

                // walk
                if (ifracx < ifracdx1) {
                    /*
                    // DEBUG
                                  s_ix += incx;
                    */
                    ifracx += ifracdx;
                    sourceXOffset += incx;
                } else {
                    /*
                    // DEBUG
                                  s_ix += incx1;
                    */
                    ifracx -= ifracdx1;
                    sourceXOffset += incx1;
                }

                if (ifracy < ifracdy1) {
                    /*
                    // DEBUG
                                  s_iy += incy;
                    */
                    ifracy += ifracdy;
                    sourceYOffset += incyStride;
                } else {
                    /*
                    // DEBUG
                                  s_iy += incy1;
                    */
                    ifracy -= ifracdy1;
                    sourceYOffset += incy1Stride;
                }

                ++destXOffset;
            }

            for (int x = clipMaxX; x < dst_max_x; x++) {

                if (setBackground) {
                    int dindex = destYOffset + (destXOffset >> 3);
                    int dshift = 7 - (destXOffset & 7);
                    int delement = destData[dindex];
                    delement |= black << dshift;
                    destData[dindex] = (byte) delement;
                }

                // walk
                if (ifracx < ifracdx1) {
                    /*
                    // DEBUG
                                  s_ix += incx;
                    */
                    ifracx += ifracdx;
                    sourceXOffset += incx;
                } else {
                    /*
                    // DEBUG
                                  s_ix += incx1;
                    */
                    ifracx -= ifracdx1;
                    sourceXOffset += incx1;
                }

                if (ifracy < ifracdy1) {
                    /*
                    // DEBUG
                                  s_iy += incy;
                    */
                    ifracy += ifracdy;
                    sourceYOffset += incyStride;
                } else {
                    /*
                    // DEBUG
                                  s_iy += incy1;
                    */
                    ifracy -= ifracdy1;
                    sourceYOffset += incy1Stride;
                }

                ++destXOffset;
            }
        }
    }

    private void shortLoop(Raster source, WritableRaster dest, Rectangle destRect) {
        float src_rect_x1 = source.getMinX();
        float src_rect_y1 = source.getMinY();
        float src_rect_x2 = src_rect_x1 + source.getWidth();
        float src_rect_y2 = src_rect_y1 + source.getHeight();

        MultiPixelPackedSampleModel sourceSM = (MultiPixelPackedSampleModel) source.getSampleModel();
        DataBufferUShort sourceDB = (DataBufferUShort) source.getDataBuffer();
        int sourceTransX = source.getSampleModelTranslateX();
        int sourceTransY = source.getSampleModelTranslateY();
        int sourceDataBitOffset = sourceSM.getDataBitOffset();
        int sourceScanlineStride = sourceSM.getScanlineStride();

        MultiPixelPackedSampleModel destSM = (MultiPixelPackedSampleModel) dest.getSampleModel();
        DataBufferUShort destDB = (DataBufferUShort) dest.getDataBuffer();
        int destMinX = dest.getMinX();
        int destMinY = dest.getMinY();
        int destTransX = dest.getSampleModelTranslateX();
        int destTransY = dest.getSampleModelTranslateY();
        int destDataBitOffset = destSM.getDataBitOffset();
        int destScanlineStride = destSM.getScanlineStride();

        short[] sourceData = sourceDB.getData();
        int sourceDBOffset = sourceDB.getOffset();

        short[] destData = destDB.getData();
        int destDBOffset = destDB.getOffset();

        Point2D dst_pt = new Point2D.Float();
        Point2D src_pt = new Point2D.Float();

        int dst_min_x = destRect.x;
        int dst_min_y = destRect.y;
        int dst_max_x = destRect.x + destRect.width;
        int dst_max_y = destRect.y + destRect.height;

        int incyStride = incy * sourceScanlineStride;
        int incy1Stride = incy1 * sourceScanlineStride;

        black = ((int) backgroundValues[0]) & 1;

        for (int y = dst_min_y; y < dst_max_y; y++) {
            // Backward map the first point in the line
            // The energy is at the (pt_x + 0.5, pt_y + 0.5)
            dst_pt.setLocation((double) dst_min_x + 0.5, (double) y + 0.5);
            mapDestPoint(dst_pt, src_pt);

            // Get the mapped source coordinates
            float s_x = (float) src_pt.getX();
            float s_y = (float) src_pt.getY();

            // Floor to get the integral coordinate
            int s_ix = (int) Math.floor(s_x);
            int s_iy = (int) Math.floor(s_y);

            double fracx = s_x - (double) s_ix;
            double fracy = s_y - (double) s_iy;

            int ifracx = (int) Math.floor(fracx * geom_frac_max);
            int ifracy = (int) Math.floor(fracy * geom_frac_max);

            int start_s_ix = s_ix;
            int start_s_iy = s_iy;
            int start_ifracx = ifracx;
            int start_ifracy = ifracy;

            // Compute clipMinX, clipMinY
            Range clipRange = performScanlineClipping(
                    src_rect_x1,
                    src_rect_y1,
                    // Last point in the source is
                    // x2 = x1 + width - 1
                    // y2 = y1 + height - 1
                    src_rect_x2 - 1,
                    src_rect_y2 - 1,
                    s_ix,
                    s_iy,
                    ifracx,
                    ifracy,
                    dst_min_x,
                    dst_max_x,
                    0,
                    0,
                    0,
                    0);
            int clipMinX = ((Integer) clipRange.getMinValue()).intValue();
            int clipMaxX = ((Integer) clipRange.getMaxValue()).intValue();

            if (clipMinX > clipMaxX) continue;

            int destYOffset = (y - destTransY) * destScanlineStride + destDBOffset;
            int destXOffset = destDataBitOffset + (dst_min_x - destTransX);

            int sourceYOffset = (s_iy - sourceTransY) * sourceScanlineStride + sourceDBOffset;
            int sourceXOffset = s_ix - sourceTransX + sourceDataBitOffset;

            for (int x = dst_min_x; x < clipMinX; x++) {

                if (setBackground) {
                    int dindex = destYOffset + (destXOffset >> 4);
                    int dshift = 15 - (destXOffset & 15);
                    int delement = destData[dindex];
                    delement |= black << dshift;
                    destData[dindex] = (short) delement;
                }

                // walk
                if (ifracx < ifracdx1) {
                    /*
                    // DEBUG
                                  s_ix += incx;
                    */
                    ifracx += ifracdx;
                    sourceXOffset += incx;
                } else {
                    /*
                    // DEBUG
                                  s_ix += incx1;
                    */
                    ifracx -= ifracdx1;
                    sourceXOffset += incx1;
                }

                if (ifracy < ifracdy1) {
                    /*
                    // DEBUG
                                  s_iy += incy;
                    */
                    ifracy += ifracdy;
                    sourceYOffset += incyStride;
                } else {
                    /*
                    // DEBUG
                                  s_iy += incy1;
                    */
                    ifracy -= ifracdy1;
                    sourceYOffset += incy1Stride;
                }

                ++destXOffset;
            }

            for (int x = clipMinX; x < clipMaxX; x++) {
                int sindex = sourceYOffset + (sourceXOffset >> 4);
                short selement = sourceData[sindex];
                int val = (selement >> (15 - (sourceXOffset & 15))) & 1;

                int dindex = destYOffset + (destXOffset >> 4);
                int dshift = 15 - (destXOffset & 15);
                int delement = destData[dindex];
                delement |= val << dshift;
                destData[dindex] = (short) delement;

                // walk
                if (ifracx < ifracdx1) {
                    /*
                    // DEBUG
                                  s_ix += incx;
                    */
                    ifracx += ifracdx;
                    sourceXOffset += incx;
                } else {
                    /*
                    // DEBUG
                                  s_ix += incx1;
                    */
                    ifracx -= ifracdx1;
                    sourceXOffset += incx1;
                }

                if (ifracy < ifracdy1) {
                    /*
                    // DEBUG
                                  s_iy += incy;
                    */
                    ifracy += ifracdy;
                    sourceYOffset += incyStride;
                } else {
                    /*
                    // DEBUG
                                  s_iy += incy1;
                    */
                    ifracy -= ifracdy1;
                    sourceYOffset += incy1Stride;
                }

                ++destXOffset;
            }

            for (int x = clipMaxX; x < dst_max_x; x++) {
                if (setBackground) {
                    int dindex = destYOffset + (destXOffset >> 4);
                    int dshift = 15 - (destXOffset & 15);
                    int delement = destData[dindex];
                    delement |= black << dshift;
                    destData[dindex] = (short) delement;
                }

                // walk
                if (ifracx < ifracdx1) {
                    /*
                    // DEBUG
                                  s_ix += incx;
                    */
                    ifracx += ifracdx;
                    sourceXOffset += incx;
                } else {
                    /*
                    // DEBUG
                                  s_ix += incx1;
                    */
                    ifracx -= ifracdx1;
                    sourceXOffset += incx1;
                }

                if (ifracy < ifracdy1) {
                    /*
                    // DEBUG
                                  s_iy += incy;
                    */
                    ifracy += ifracdy;
                    sourceYOffset += incyStride;
                } else {
                    /*
                    // DEBUG
                                  s_iy += incy1;
                    */
                    ifracy -= ifracdy1;
                    sourceYOffset += incy1Stride;
                }

                ++destXOffset;
            }
        }
    }

    private void intLoop(Raster source, WritableRaster dest, Rectangle destRect) {
        float src_rect_x1 = source.getMinX();
        float src_rect_y1 = source.getMinY();
        float src_rect_x2 = src_rect_x1 + source.getWidth();
        float src_rect_y2 = src_rect_y1 + source.getHeight();

        MultiPixelPackedSampleModel sourceSM = (MultiPixelPackedSampleModel) source.getSampleModel();
        DataBufferInt sourceDB = (DataBufferInt) source.getDataBuffer();
        int sourceTransX = source.getSampleModelTranslateX();
        int sourceTransY = source.getSampleModelTranslateY();
        int sourceDataBitOffset = sourceSM.getDataBitOffset();
        int sourceScanlineStride = sourceSM.getScanlineStride();

        MultiPixelPackedSampleModel destSM = (MultiPixelPackedSampleModel) dest.getSampleModel();
        DataBufferInt destDB = (DataBufferInt) dest.getDataBuffer();
        int destMinX = dest.getMinX();
        int destMinY = dest.getMinY();
        int destTransX = dest.getSampleModelTranslateX();
        int destTransY = dest.getSampleModelTranslateY();
        int destDataBitOffset = destSM.getDataBitOffset();
        int destScanlineStride = destSM.getScanlineStride();

        int[] sourceData = sourceDB.getData();
        int sourceDBOffset = sourceDB.getOffset();

        int[] destData = destDB.getData();
        int destDBOffset = destDB.getOffset();

        Point2D dst_pt = new Point2D.Float();
        Point2D src_pt = new Point2D.Float();

        int dst_min_x = destRect.x;
        int dst_min_y = destRect.y;
        int dst_max_x = destRect.x + destRect.width;
        int dst_max_y = destRect.y + destRect.height;

        int incyStride = incy * sourceScanlineStride;
        int incy1Stride = incy1 * sourceScanlineStride;

        black = ((int) backgroundValues[0]) & 1;

        for (int y = dst_min_y; y < dst_max_y; y++) {
            // Backward map the first point in the line
            // The energy is at the (pt_x + 0.5, pt_y + 0.5)
            dst_pt.setLocation((double) dst_min_x + 0.5, (double) y + 0.5);
            mapDestPoint(dst_pt, src_pt);

            // Get the mapped source coordinates
            float s_x = (float) src_pt.getX();
            float s_y = (float) src_pt.getY();

            // Floor to get the integral coordinate
            int s_ix = (int) Math.floor(s_x);
            int s_iy = (int) Math.floor(s_y);

            double fracx = s_x - (double) s_ix;
            double fracy = s_y - (double) s_iy;

            int ifracx = (int) Math.floor(fracx * geom_frac_max);
            int ifracy = (int) Math.floor(fracy * geom_frac_max);

            int start_s_ix = s_ix;
            int start_s_iy = s_iy;
            int start_ifracx = ifracx;
            int start_ifracy = ifracy;

            // Compute clipMinX, clipMinY
            Range clipRange = performScanlineClipping(
                    src_rect_x1,
                    src_rect_y1,
                    // Last point in the source is
                    // x2 = x1 + width - 1
                    // y2 = y1 + height - 1
                    src_rect_x2 - 1,
                    src_rect_y2 - 1,
                    s_ix,
                    s_iy,
                    ifracx,
                    ifracy,
                    dst_min_x,
                    dst_max_x,
                    0,
                    0,
                    0,
                    0);
            int clipMinX = ((Integer) clipRange.getMinValue()).intValue();
            int clipMaxX = ((Integer) clipRange.getMaxValue()).intValue();

            if (clipMinX > clipMaxX) continue;

            int destYOffset = (y - destTransY) * destScanlineStride + destDBOffset;
            int destXOffset = destDataBitOffset + (dst_min_x - destTransX);

            int sourceYOffset = (s_iy - sourceTransY) * sourceScanlineStride + sourceDBOffset;
            int sourceXOffset = s_ix - sourceTransX + sourceDataBitOffset;

            for (int x = dst_min_x; x < clipMinX; x++) {
                if (setBackground) {
                    int dindex = destYOffset + (destXOffset >> 5);
                    int dshift = 31 - (destXOffset & 31);
                    int delement = destData[dindex];
                    delement |= black << dshift;
                    destData[dindex] = (int) delement;
                }

                // walk
                if (ifracx < ifracdx1) {
                    /*
                    // DEBUG
                                  s_ix += incx;
                    */
                    ifracx += ifracdx;
                    sourceXOffset += incx;
                } else {
                    /*
                    // DEBUG
                                  s_ix += incx1;
                    */
                    ifracx -= ifracdx1;
                    sourceXOffset += incx1;
                }

                if (ifracy < ifracdy1) {
                    /*
                    // DEBUG
                                  s_iy += incy;
                    */
                    ifracy += ifracdy;
                    sourceYOffset += incyStride;
                } else {
                    /*
                    // DEBUG
                                  s_iy += incy1;
                    */
                    ifracy -= ifracdy1;
                    sourceYOffset += incy1Stride;
                }

                ++destXOffset;
            }

            for (int x = clipMinX; x < clipMaxX; x++) {
                int sindex = sourceYOffset + (sourceXOffset >> 5);
                int selement = sourceData[sindex];
                int val = (selement >> (31 - (sourceXOffset & 31))) & 1;

                int dindex = destYOffset + (destXOffset >> 5);
                int dshift = 31 - (destXOffset & 31);
                int delement = destData[dindex];
                delement |= val << dshift;
                destData[dindex] = (int) delement;

                // walk
                if (ifracx < ifracdx1) {
                    /*
                    // DEBUG
                                  s_ix += incx;
                    */
                    ifracx += ifracdx;
                    sourceXOffset += incx;
                } else {
                    /*
                    // DEBUG
                                  s_ix += incx1;
                    */
                    ifracx -= ifracdx1;
                    sourceXOffset += incx1;
                }

                if (ifracy < ifracdy1) {
                    /*
                    // DEBUG
                                  s_iy += incy;
                    */
                    ifracy += ifracdy;
                    sourceYOffset += incyStride;
                } else {
                    /*
                    // DEBUG
                                  s_iy += incy1;
                    */
                    ifracy -= ifracdy1;
                    sourceYOffset += incy1Stride;
                }

                ++destXOffset;
            }

            for (int x = clipMaxX; x < dst_max_x; x++) {
                if (setBackground) {
                    int dindex = destYOffset + (destXOffset >> 5);
                    int dshift = 31 - (destXOffset & 31);
                    int delement = destData[dindex];
                    delement |= black << dshift;
                    destData[dindex] = (int) delement;
                }

                // walk
                if (ifracx < ifracdx1) {
                    /*
                    // DEBUG
                                  s_ix += incx;
                    */
                    ifracx += ifracdx;
                    sourceXOffset += incx;
                } else {
                    /*
                    // DEBUG
                                  s_ix += incx1;
                    */
                    ifracx -= ifracdx1;
                    sourceXOffset += incx1;
                }

                if (ifracy < ifracdy1) {
                    /*
                    // DEBUG
                                  s_iy += incy;
                    */
                    ifracy += ifracdy;
                    sourceYOffset += incyStride;
                } else {
                    /*
                    // DEBUG
                                  s_iy += incy1;
                    */
                    ifracy -= ifracdy1;
                    sourceYOffset += incy1Stride;
                }

                ++destXOffset;
            }
        }
    }
}
